// 界面交互、反馈类

/**
 * 延时器类（setTimeout）
 * 
 * 延长指定时间后再执行
 */
class Delayer{
    /**
     * 启动
     * @param {number} delay 延时时间，单位：毫秒 
     * @returns {Promise<>}
     */
    start(delay){
        return (new Promise((resolve, reject)=>{
            if(this._delayerNo){
                console.error('Timer重复启动')
                return
                // reject(new Error('Timer已经启动，无法再次启动'))
            }
            this._delayerNo = setTimeout(()=>{
                resolve()
            }, delay)
        }))
    }
    end(){
        clearTimeout(this._delayerNo)
        this._delayerNo = 0
    }
    constructor(){
        this._delayerNo = 0
    }
}
/**
 * 延时器实例管理类
 * 每种类型的Delayer只有一个实例
 */
class DelayerUtil{
    /**
     * 获取showLoading的延时器
     * @returns {Delayer}
     */
    static getLoadingDelayer(){
        if(!(this._loadingDelayer instanceof Delayer)){
            this._loadingDelayer = new Delayer()
        }
        return this._loadingDelayer
    }
    /**
     * 获取Toast的延时器
     * @returns {Delayer}
     */
    static getToastDelayer(){
        if(!(this._toastDelayer instanceof Delayer)){
            this._toastDelayer = new Delayer()
        }
        return this._toastDelayer
    }
}

/**
 * 界面交互工具类
 */
export default class InteractionUtil
{
    /**
     * 显示 loading 提示框
     * 
     * @example 
     * 使用默认提示内容
     * ```
     * InteractionUtil.showLoading(250)
     * 
     * InteractionUtil.showLoading(250, '')
     * ```
     * 
     * 自定义提示内容
     * ```
     * InteractionUtil.showLoading(300, '请稍后...')
     * ```
     * 
     * 显示提示内容后再执行操作
     * ```
     * InteractionUtil.showLoading(250).then(()=>{console.log('showLoading success')})
     * ```
     * @param {number} delay 延时执行时间，单位：毫秒
     * @param {string} title 提示的内容,内容为空时使用默认内容
     * @param {boolean} withMask 是否显示透明蒙层，防止触摸穿透
     */
    static showLoading(delay = 0, title = '', withMask = false){
        return DelayerUtil.getLoadingDelayer()
        .start(delay)
        .then(()=>{
            return (new Promise((resolve, reject)=>{
                wx.showLoading({
                    title: title?title:'正在加载...',
                    mask: withMask,
                    success: (result) => {
                        resolve(result.errMsg)
                    },
                    fail: (err) => {
                        reject(err.errMsg)
                    },
                });
            }))
        })
    }

    /**
     * 隐藏 loading 提示框
     * 
     * @example
     * 立即隐藏
     * ```
     * InteractionUtil.hideLoading()
     * ```
     * 延时隐藏
     * ```
     * InteractionUtil.hideLoading(300); // api返回数据后，页面的图片的加载可能还需要一段时间，此时延时隐藏可以起到提示用户继续等待片刻的作用
     * ```
     * 隐藏提示内容后再执行操作
     * ```
     * InteractionUtil.hideLoading().then(()=>{console.log('“正在加载中”的提示已经隐藏')})
     * ```
     * @param {number} delay 延时时长，单位：毫秒 通过设置该值，可以起到延期隐藏"loading"提示的作用
     * @returns {Promise<>}
     */
    static hideLoading(delay=0){
        let hideProm, doHide = function(){
            DelayerUtil.getLoadingDelayer().end()
            wx.hideLoading()
        }
        if(delay > 0){
            hideProm = (new Promise((resolve)=>{
                setTimeout(()=>{
                    doHide()
                    resolve()
                }, delay)
            }))
        }else{
            doHide()
            hideProm = Promise.resolve()
        }
        return hideProm
    }
    /**
     * 提示成功
     * @param {string} content 
     */
    static showSuccess(content){
        let toast = this.getToast()
        toast.title = content
        toast.icon='success'
        return toast.show()
    }
    /**
     * 警告信息（不打断流程）
     * @param {string} content 
     */
    static showNotice(content){
        let toast = this.getToast()
        toast.title = content
        toast.duration = 2000
        toast.image='/pages/images/fail.png'
        return toast.show()
    }
    /**
     * 展示警告信息（打断流程）
     * 类似`window.alert()`
     * 
     * @example 明确告知用户
     * ```
     *  InteractionUtil.showAlert('当前网路不给力，请稍后再试')
     * ```
     * @param {string} content 显示的内容
     * @param {string} title 显示的标题
     * @returns {Promise<ModalOptionResult, Error>} 用户操作后返回`ModalOptionResult`，调用失败返回Error
     */
    static showAlert(content, title = ''){
        let modal = this.getModal()    
        modal.content = content
        if(title) modal.title = title
        modal.showCancel = false
        return modal.show()
    }
    /**
     * 向用户展示确认弹窗（打断流程）
     * 类似`window.confirm()`
     * 
     * @example 询问用户下一步的操作
     * ```
     *  InteractionUtil.showConfirm('当前网路不给力，再试一次？').then(res=>{
     *     if(res.confirm){
     *       console.log('点击 确认')
     *     }else if(res.cancel){
     *       console.log('点击 取消')
     *     }else{
     *       console.log('未做选择')
     *     }
     *  })
     * ```
     * @param {string} content 显示的内容
     * @param {string} title 显示的标题
     * @returns {Promise<ModalOptionResult, Error>} 用户操作后返回`ModalOptionResult`，调用失败返回Error
     */
    static showConfirm(content, title = ''){
        let modal = this.getModal()
        modal.title = title
        modal.content = content
        modal.showCancel = true
        return modal.show()
    }
    /**
     * 获取可交互的提示框
     * @returns {Modal}
     */
    static getModal(){
        return new Modal()
    }

    /**
     * 获取toast实例
     */
    static getToast(){
        return new Toast()
    }
}

/**
 * @typedef {Object} ModalOptionResult 模态框操作结果
 * @property {boolean} confirm 为 true 时，表示用户点击了确定按钮
 * @property {boolean} cancel 为 true 时，表示用户点击了取消（用于 Android 系统区分点击蒙层关闭还是点击取消按钮关闭）
 */

/**
 * 模态对话框
 */
class Modal {
    /**
     * @param {string} title 提示的标题
     */
    set title(title) {
        this._data.title = title
    }
    /**
     * @param {string} content 提示的内容
     */
    set content(content) {
        this._data.content = content
    }
    /**
     * @param {boolean} showCancel 是否显示取消按钮,默认true
     */
    set showCancel(showCancel = true) {
        this._data.showCancel = Boolean(showCancel)
    }
    /**
     * 取消按钮的文字，最多 4 个字符,默认'取消' 
     * @param {string} cancelText 
     */
    set cancelText(cancelText = '取消') {
        this._data.cancelText = cancelText
    }
    /**
     * 取消按钮的文字颜色，必须是 16 进制格式的颜色字符串,默认#000000
     * @param {string} cancelColor 
     */
    set cancelColor(cancelColor = '#000000') {
        this._data.cancelColor = cancelColor
    }
    /**
     * 确认按钮的文字，最多 4 个字符,默认'确定'
     * @param {string} confirmText 
     */
    set confirmText(confirmText = '确定') {
        this._data.confirmText = confirmText
    }
    /**
     * 确认按钮的文字颜色，必须是 16 进制格式的颜色字符串，默认#576B95
     * @param {string} confirmColor 
     */
    set confirmColor(confirmColor = '#576B95') {
        this._data.confirmColor = confirmColor
    }
    constructor(){
        this._data={}
    }
    /**
     * 展示模态框
     * @returns {Promise<ModalOptionResult, Error>} 模态框正常关闭，返回`ModalOptionResult`；展示失败返回错误
     */
    show(){
        return (new Promise((resolve, reject)=>{
            wx.showModal({
                ...this._data,
                success: (result) => {
                    resolve(result)
                },
                fail: (err) => {
                    reject(new Error(err.errMsg))
                }
            });
        }))
    }
}

/**
 * toast弹框类
 */
class Toast {
    /**
     * @param {string} title 提示的内容
     */
    set title(title) {
        this._data.title = title
    }
    /**
     * @returns {string}
     */
    get title(){
        return String(this._data.title)
    }
    /**
     * @param {string} icon 图标，默认success;success:显示成功图标,loading:显示加载图标,none:不显示图标，此时 title 文本最多可显示两行
     */
    set icon(icon='success') {
        this._data.icon = icon
    }
    /**
     * @param {string} image 自定义图标的本地路径，image 的优先级高于 icon
     */
    set image(image) {
        this._data.image = image
    }
    /**
     * 提示的展示时间 
     * @param {string} duration 单位：毫秒 
     */
    set duration(duration) {
        this._data.duration = duration
    }
    get duration(){
        return !this._data.duration?0:this._data.duration
    }
    /**
     * 是否显示透明蒙层，防止触摸穿透 	
     * @param {boolean} mask 
     */
    set mask(mask = false) {
        this._data.mask = Boolean(mask)
    }
    constructor(){
        this._data={}
    }
    /**
     * 显示消息提示框
     * @param {number} delay 延迟多久后弹出内容
     * @returns {Promise<string,Error>}
     */
    show(delay=0){
        if(this.title.length > 7){
            this.icon='none'
            this.image=''
            if(!this.duration) this.duration = 3000
        }
        return DelayerUtil.getToastDelayer()
        .start(delay)
        .then(()=>{
            return (new Promise((resolve, reject)=>{
                wx.showToast({
                    ...this._data,
                    success: (result) => {
                        resolve(result.errMsg)
                    },
                    fail: (err) => {
                        reject(new Error(err.errMsg))
                    },
                    complete: ()=>{
                        DelayerUtil.getToastDelayer().end()
                    }
                })
            }))
        })
    }
    /**
     * 隐藏弹框
     * @param {number} delay 延迟多久后隐藏弹框
     */
    hide(delay){
        let hideProm, doHide = function(){
            DelayerUtil.getToastDelayer().end()
            wx.hideToast()
        }
        if(delay > 0){
            hideProm = (new Promise((resolve)=>{
                setTimeout(()=>{
                    doHide()
                    resolve()
                }, delay)
            }))
        }else{
            doHide()
            hideProm = Promise.resolve()
        }
        return hideProm
    }
}
